package net.quanter.sae;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.sae.model.v20190506.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

public class DeploySAE {

    private static final String WINDOWS = "WINDOWS";
    private static final String LINUX = "LINUX";

    private static Logger info_logger = LoggerFactory.getLogger("info-logger");
    private static Logger error_logger = LoggerFactory.getLogger("error-logger");
    private static Logger root_logger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);

    private static final String url_prefix_key = "url_prefix_key";
    private static final String regionId_key = "regionId_key";
    private static final String root_deploy_key = "root_deploy_key";
    private static final String retain_file_num_key = "retain_file_num_key";   //最多保留几个发布版本
    private static final String accessid_key = "accessid_key";
    private static final String secret_key = "secret_key";
    private static final String sleep_time_key = "sleep_time_key";
    private static final String retry_num_key = "retry_num_key";

    private static final String runningStatus = "running";
    private final static Integer startStatus = new Integer(1);
    private final static Integer successStatus = new Integer(2);
    private final static Integer failStatus = new Integer(3);
    private final static Properties properties = new Properties();
    private final static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
    private final static String dateStr = dateFormat.format(new Date());

    public static void main(String[] args) throws Exception {
        try {
            run(args);
        } catch (Exception e) {
            error_logger.error(e.getMessage());
            root_logger.error(e.getMessage());
            throw e;
        }
    }

    static String regionId;
    static String accessId;
    static String secret;
    static String url_prifix;
    static int retry_num;
    static long sleep_time;
    static String root_deploy_dir;
    static int retain_file_num;
    static IAcsClient client;

    private static void run(String[] args) throws Exception {


        if (args.length < 2) {
            String errorMessage = String.format("启动参数错误[%s]!", joinString(args));
            throw new Exception(errorMessage);
        }
        String command = args[0];
        String osname = System.getProperty("os.name");
        String osname_upper = System.getProperty("os.name").toUpperCase();
        InputStream inputStream;
        if (osname_upper.contains(WINDOWS)) {
            info_logger.info(String.format("操作系统: %s, 加载配置config.properties", osname));
            inputStream = DeploySAE.class.getClassLoader().getResourceAsStream("deploy.properties");
        } else if (osname_upper.contains(LINUX)) {
            //必须cd 到配置文件目录下才能运行
            File file = new File("deploy.properties");
            if (file.exists() && file.isFile() && file.canRead()) {
                info_logger.info(String.format("操作系统: %s,加载配置,位置:%s", osname, file.getAbsolutePath()));
                inputStream = new FileInputStream(file);
            } else {
                String errorMessage = String.format("读配置文件[%s]错误! ", file.getPath());
                throw new Exception(errorMessage);
            }
        } else {
            String errorMessage = String.format("操作系统: %s , 不能运行程序, 退出!");
            throw new Exception(errorMessage);
        }
        properties.load(inputStream);
        if (
                !properties.containsKey(regionId_key) ||
                        !properties.containsKey(accessid_key) ||
                        !properties.containsKey(secret_key) ||
                        !properties.containsKey(url_prefix_key) ||
                        !properties.containsKey(retry_num_key) ||
                        !properties.containsKey(sleep_time_key)) {
            String errorMessage = String.format("丢失参数，检查配置config.properties里是否有[regionId_key,accessid_key,secret_key,url_prefix_key,retry_num_key,sleep_time_key]");
            throw new Exception(errorMessage);
        }
        regionId = properties.getProperty(regionId_key);
        accessId = properties.getProperty(accessid_key);
        secret = properties.getProperty(secret_key);
        url_prifix = properties.getProperty(url_prefix_key);
        retry_num = Integer.parseInt(properties.getProperty(retry_num_key));
        sleep_time = Long.parseLong(properties.getProperty(sleep_time_key));
        root_deploy_dir = properties.getProperty(root_deploy_key);
        retain_file_num = Integer.parseInt(properties.getProperty(retain_file_num_key));

        DefaultProfile profile = DefaultProfile.getProfile(regionId, accessId, secret);
        client = new DefaultAcsClient(profile);
        switch (command) {
            case "deploy":
                if (args.length < 3) {
                    String errorMessage = String.format("执行deploy启动参数错误[%s]，需要3个参数，现在只有%s个", joinString(args), args.length);
                    throw new Exception(errorMessage);
                }
                String appId = args[1];
                String jar = args[2];
                deploy(appId, jar);
                break;
            case "fulldeploy":
                if (args.length < 3) {
                    String errorMessage = String.format("执行fulldeploy启动参数错误[%s]，需要3个参数，现在只有%s个", joinString(args), args.length);
                    throw new Exception(errorMessage);
                }
                appId = args[1];
                jar = args[2];
                fulldeploy(appId, jar);
                break;
        }
    }

    //先移动，重命名，再部署
    private static final void fulldeploy(String appId, String jar) throws Exception {

        if (!properties.containsKey(root_deploy_key)
                || !properties.containsKey(root_deploy_key)) {
            String errorMessage = String.format("丢失参数，检查配置config.properties里是否有[root_deploy_key]");
            throw new Exception(errorMessage);
        }
        //step.1 先查询应用信息
        DescribeApplicationConfigRequest configRequest = new DescribeApplicationConfigRequest();
        configRequest.setAppId(appId);
        DescribeApplicationConfigResponse describeApplicationConfigResponse = client.getAcsResponse(configRequest);
        if (!describeApplicationConfigResponse.getSuccess()) {
            String errorMessage = String.format("appId[%s], 应用不存在:%s", appId, describeApplicationConfigResponse.getMessage());
            throw new Exception(errorMessage);
        }
        DescribeApplicationConfigResponse.Data configResponseData = describeApplicationConfigResponse.getData();
        String env = configResponseData.getNamespaceId().split(":")[1];

        File oldFile = new File(root_deploy_dir + jar);
        String oldJarPreName = jar.split("[.]")[0];//老的文件名
        File newJarDir = new File(root_deploy_dir + env);//部署目录

        if (newJarDir.isDirectory()) {
            File[] files = newJarDir.listFiles(x -> x.getName().startsWith(oldJarPreName));
            List<File> fileList = Arrays.asList(files);
            fileList = fileList.stream().filter(x->x.isFile()).sorted((a,b)->(int)(b.lastModified() - a.lastModified())).collect(Collectors.toList());//按时间倒排序
            List<File> subList = fileList.size() > retain_file_num-1?fileList.subList(retain_file_num-1,fileList.size()):new ArrayList<>();//要删除的文件
            for (File file : subList) {
                info_logger.info("删除老的jar文件:"+file.getName());
                file.delete();//删除多余的文件
            }
        }

        String newJar = env + "/" + jar.replaceAll("\\.jar", "-" + dateStr + ".jar");
        if (oldFile.isFile()) {
            File newFile = new File(root_deploy_dir + newJar);
            String message = String.format("移动jar文件从[%s]到[%s]", oldFile.getPath(), newFile.getPath());
            root_logger.info(message);
            oldFile.renameTo(newFile);
            deploy(appId, newJar);
        } else {
            String errorMessage = String.format("jar[%s]不存在", oldFile.getPath());
            throw new Exception(errorMessage);
        }
    }

    //直接部署
    private static final void deploy(String appId, String jar) throws Exception {

        String url = url_prifix + jar;
        String message = String.format("appId[%s],jar[%s],url[%s] 开始部署", appId, jar, url);
        info_logger.info(message);
        //step.1 先查询应用信息
        DescribeApplicationConfigRequest configRequest = new DescribeApplicationConfigRequest();
        configRequest.setAppId(appId);
        DescribeApplicationConfigResponse describeApplicationConfigResponse = client.getAcsResponse(configRequest);
        if (!describeApplicationConfigResponse.getSuccess()) {
            String errorMessage = String.format("appId[%s], 应用不存在:%s", appId, describeApplicationConfigResponse.getMessage());
            throw new Exception(errorMessage);
        }
        DescribeApplicationConfigResponse.Data configResponseData = describeApplicationConfigResponse.getData();

        String appname = configResponseData.getAppName();

        /* step.2 开始deploy */
        DeployApplicationRequest deployApplicationRequest = new DeployApplicationRequest();
        deployApplicationRequest.setSysRegionId(regionId);
        deployApplicationRequest.setAppId(appId);
        deployApplicationRequest.setPackageUrl(url);
        deployApplicationRequest.setPackageVersion(dateStr);
        deployApplicationRequest.setJdk(configResponseData.getJdk());
        deployApplicationRequest.setEdasContainerVersion(configResponseData.getEdasContainerVersion());
        deployApplicationRequest.setMinReadyInstances(1);
        deployApplicationRequest.setJarStartOptions("-Dfile.encoding=UTF8");
        DeployApplicationResponse deployApplicationResponse = client.getAcsResponse(deployApplicationRequest);

        if (!deployApplicationResponse.getSuccess() || deployApplicationResponse.getData().getChangeOrderId() == null) {

            String errorMessage = String.format("appname[%s],appId[%s] 上传包失败:%s", appname, appId, deployApplicationResponse.getMessage());
            throw new Exception(errorMessage);
        } else {
            String infoMessage = String.format("appname[%s],appId[%s] ,jar[%s] 上传包成功,准备检测启动状态", appname, appId, jar);
            info_logger.info(infoMessage);
        }

        String changeOrderId = deployApplicationResponse.getData().getChangeOrderId();
        //step.4 查询变更记录(这里要循环查询)
        for (int i = 0; i < retry_num; i++) {
            DescribeChangeOrderRequest changeOrderRequest = new DescribeChangeOrderRequest();
            changeOrderRequest.setChangeOrderId(changeOrderId);
            DescribeChangeOrderResponse response = client.getAcsResponse(changeOrderRequest);
            if (!response.getSuccess()) {
                Thread.sleep(sleep_time);//每次失败等10秒
                continue;
            }
            DescribeChangeOrderResponse.Data changeOrderData = response.getData();
            Integer status = changeOrderData.getStatus();
            if (startStatus.equals(status)) {
                message = String.format("appname[%s],appId[%s],changeOrderId[%s] 启动状态检查中", appname, appId, changeOrderId);
                root_logger.info(message);
                Thread.sleep(sleep_time);//每次失败等10秒
                continue;
            } else if (successStatus.equals(status)) {
                message = String.format("appname[%s],appId[%s],changeOrderId[%s] 启动成功，最后一步，检测应用运行状态", appname, appId, changeOrderId);
                root_logger.info(message);
                break;
            } else if (failStatus.equals(status)) {
                message = String.format("appname[%s],appId[%s],changeOrderId[%s] 启动失败", appname, appId, changeOrderId);
                throw new Exception(message);
            }
        }

        //step.3 查询应用状态
        DescribeApplicationStatusRequest request = new DescribeApplicationStatusRequest();
        request.setSysRegionId(regionId);
        request.setAppId(appId);
        DescribeApplicationStatusResponse response = client.getAcsResponse(request);
        if (!response.getSuccess() || !runningStatus.equalsIgnoreCase(response.getData().getCurrentStatus())) {
            message = String.format("appname[%s],appId[%s] 应用状态检测为非运行状态[%s]", appname, appId, response.getData().getCurrentStatus());
            throw new Exception(message);
        } else {
            message = String.format("appname[%s],appId[%s] 应用状态检测为运行状态[%s],应用部署成功", appname, appId, response.getData().getCurrentStatus());
            info_logger.info(message);
        }
    }

    private static final String joinString(String[] args) {
        if (args == null) {
            return "";
        } else {
            StringBuilder sb = new StringBuilder();
            for (String arg : args) {
                sb.append(arg).append(" ");
            }
            return sb.toString();
        }
    }
}
